﻿#include "snake.h"

/// <summary>
/// 设置控制台光标坐标
/// </summary>
/// <param name="x">设置到x坐标</param>
/// <param name="y">设置到y坐标</param>
void SetCurPos(short x, short y)
{
	//定义光标坐标
	COORD pos = { x,y };
	//获得设备句柄
	HANDLE handput = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置光标位置
	SetConsoleCursorPosition(handput, pos);
}

/// <summary>
/// 设置光标影藏
/// </summary>
void VisibleCur()
{
	//定义光标
	CONSOLE_CURSOR_INFO cursorinfo = { 0 };
	//获得设备句柄
	HANDLE handput = GetStdHandle(STD_OUTPUT_HANDLE);
	//获得光标状态
	GetConsoleCursorInfo(handput, &cursorinfo);
	//设置光标隐藏
	cursorinfo.bVisible = false;
	//设置光标状态
	SetConsoleCursorInfo(handput, &cursorinfo);
}

/// <summary>
/// 设置光标显示
/// </summary>
void UnVisibleCur()
{
	//定义光标
	CONSOLE_CURSOR_INFO cursorinfo = { 0 };
	//获得设备句柄
	HANDLE handput = GetStdHandle(STD_OUTPUT_HANDLE);
	//获得光标状态
	GetConsoleCursorInfo(handput, &cursorinfo);
	//设置光标隐藏
	cursorinfo.bVisible = true;
	//设置光标状态
	SetConsoleCursorInfo(handput, &cursorinfo);
}

/// <summary>
/// 打印环境界面
/// </summary>
void WelcomeToGame()
{
	system("cls");
	SetCurPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	SetCurPos(42, 20);
	system("pause");
	system("cls");
	SetCurPos(25, 14);
	wprintf(L"用↑↓←→来控制蛇的移动，按F3加速，按F4减速");
	SetCurPos(25, 15);
	wprintf(L"加速能够得到更高的分数");
	SetCurPos(42, 20);
	system("pause");
	system("cls");
}

/// <summary>
/// 创建游戏地图
/// </summary>
void InitMep()
{
	int i = 0;
	//打印第一行
	SetCurPos(0, 0);
	for (i = 0;i < 28;i++)
	{
		wprintf(WALL);
	}
	//打印最后一行
	SetCurPos(0, 26);
	for (i = 0;i < 28;i++)
	{
		wprintf(WALL);
	}
	//打印左边
	for (i = 0;i < 27;i++)
	{
		SetCurPos(0, i);
		wprintf(WALL);
	}
	//打印右边
	for (i = 0;i < 27;i++)
	{
		SetCurPos(56, i);
		wprintf(WALL);
	}
}

/// <summary>
/// 初始化蛇
/// </summary>
/// <param name="snake">蛇的状态地址</param>
void InitSnake(PSnake snake)
{
	assert(snake);
	//创建蛇身
	PSnakeSList mynode = NULL;
	int i = 0;
	for (i = 0;i < 5;i++)
	{
		//创建蛇⾝的节点
		mynode = (PSnakeSList)malloc(sizeof(SnakeSList));
		assert(mynode);
		//设置坐标
		mynode->data.x = (POS_X + (i * 2));
		mynode->data.y = POS_Y;
		//头插法插入链表
		if (snake->pSnake == NULL)
		{
			snake->pSnake = mynode;
			mynode->next = NULL;
		}
		else
		{
			mynode->next = snake->pSnake;
			snake->pSnake = mynode;
		}
	}
	//打印蛇身
	mynode = snake->pSnake;
	while (mynode)
	{
		SetCurPos(mynode->data.x, mynode->data.y);
		wprintf(SELF);
		mynode = mynode->next;
	}
	//初始化贪吃蛇数据
	snake->sleep_time = 200;
	//食物的分数初始化不能在创建食物的函数，否者与速度不匹配
	snake->food_score = 10;
	snake->all_score = 0;
	snake->state = OK;
	snake->dir = RIGHT;
}

/// <summary>
/// 创建链表空间
/// </summary>
/// <param name="pos">空间内存放的值</param>
/// <returns>创建的节点指针</returns>
PSnakeSList newNode(Snakepos pos)
{
	PSnakeSList newnode = (PSnakeSList)malloc(sizeof(SnakeSList));
	assert(newnode);
	newnode->data = pos;
	newnode->next = NULL;
	return newnode;
}

/// <summary>
/// 初始化食物
/// </summary>
/// <param name="snake">蛇的状态地址</param>
void InitFood(PSnake snake)
{
	assert(snake);
	Snakepos pos = { 0 };
	//随机生成一个食物的坐标
again:
	pos.x = FOOD_X;
	pos.y = FOOD_Y;
	//判断食物的坐标是否与蛇的身体重合
	PSnakeSList mysnake = snake->pSnake;
	while (mysnake)
	{
		if ((pos.x == mysnake->data.x) && (pos.y == mysnake->data.y))
		{
			goto again;
		}
		mysnake = mysnake->next;
	}
	//创建食物的节点
	PSnakeSList foodnode = newNode(pos);
	snake->pFood = foodnode;
	//打印食物
	SetCurPos(snake->pFood->data.x, snake->pFood->data.y);
	wprintf(FOOD);
}

/// <summary>
/// 游戏初始化
/// </summary>
/// <param name="snake">蛇的状态地址</param>
void GameInit(PSnake snake)
{
	//打印环境界面
	// 设置窗口大小名字
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//隐藏光标
	VisibleCur();
	//打印环境界面//功能介绍
	WelcomeToGame();
	//绘制地图
	InitMep();
	//创建蛇
	InitSnake(snake);
	//创建食物
	InitFood(snake);
}

/// <summary>
/// 打印帮助信息
/// </summary>
void PrintHelpInfo()
{
	SetCurPos(64, 15);
	wprintf(L"不能穿墙，不能咬到自己");
	SetCurPos(64, 16);
	wprintf(L"用↑↓←→来控制蛇的移动");
	SetCurPos(64, 17);
	wprintf(L"按F3加速，按F4减速");
	SetCurPos(64, 18);
	wprintf(L"按ESC退出游戏，按空格暂停");
}

/// <summary>
/// 暂停游戏
/// </summary>
void Pause()
{
	SetCurPos(64, 5);
	wprintf(L"游戏暂停");
	while (1)
	{
		Sleep(200);
		if ((GET_KEY(VK_SPACE)) || (GET_KEY(VK_ESCAPE)))
		{
			SetCurPos(64, 5);
			wprintf(L"        ");
			return;
		}
	}
}

/// <summary>
/// 判断下一个节点是不是食物
/// </summary>
/// <param name="snake">蛇的状态地址</param>
/// <param name="pos">食物的地址</param>
int NextIsFood(PSnake snake, Snakepos pos)
{
	assert(snake);
	return ((snake->pFood->data.x == pos.x) && (snake->pFood->data.y == pos.y));
}

/// <summary>
/// 蛇吃食物-打印下一个食物
/// </summary>
/// <param name="snake">蛇的状态地址</param>
void EatFood(PSnake snake)
{
	assert(snake);
	//头插法插入蛇的身体
	snake->pFood->next = snake->pSnake;
	snake->pSnake = snake->pFood;
	//将食物打印成蛇的身体
	SetCurPos(snake->pSnake->data.x, snake->pSnake->data.y);
	wprintf(SELF);
	//分数处理
	snake->all_score += snake->food_score;
	//创建新的食物
	snake->pFood = NULL;
	InitFood(snake);
}

/// <summary>
/// 刷新蛇的身体
/// </summary>
/// <param name="snake">蛇的状态地址</param>
/// <param name="pos">下一个节点的地址</param>
void RefreshSnake1(PSnake snake, Snakepos pos)
{
	assert(snake);
	//创建下一个节点
	PSnakeSList nextnode = newNode(pos);
	//头插法
	nextnode->next = snake->pSnake;
	snake->pSnake = nextnode;
	//打印新的节点
	SetCurPos(snake->pSnake->data.x, snake->pSnake->data.y);
	wprintf(SELF);
	//找蛇的尾巴
	PSnakeSList tempnode = nextnode->next;
	while (tempnode->next)
	{
		nextnode = tempnode;
		tempnode = tempnode->next;
	}
	nextnode->next = NULL;
	//打印成空格space
	SetCurPos(tempnode->data.x, tempnode->data.y);
	printf("  ");
	//释放节点
	free(tempnode);
	tempnode = NULL;
}

/// <summary>
/// 检测蛇是否撞墙
/// </summary>
/// <param name="snake">蛇的状态地址</param>
/// <param name="pos">下一个节点的地址</param>
void KillByWall(PSnake snake, Snakepos pos)
{
	assert(snake);
	if ((pos.x == 0) || (pos.x == 56) || (pos.y == 0) || (pos.y == 26))
	{
		snake->state = KILL_BY_WALL;
		SetCurPos(64, 5);
		wprintf(L"撞到了墙壁");
	}
}

/// <summary>
/// 检测蛇是否撞到自己
/// </summary>
/// <param name="snake">蛇的状态地址</param>
/// <param name="">下一个节点的地址</param>
void KillBySelf(PSnake snake, Snakepos pos)
{
	assert(snake);
	//找蛇的尾巴
	PSnakeSList tempnode = snake->pSnake;
	while (tempnode)
	{
		if ((tempnode->data.x == pos.x) && ((tempnode->data.y == pos.y)))
		{
			snake->state = KILL_BY_SELF;
			SetCurPos(64, 5);
			wprintf(L"撞到了自己");
			return;
		}
		tempnode = tempnode->next;
	}
}

/// <summary>
/// 蛇身体的移动
/// </summary>
/// <param name="">蛇的状态地址</param>
void SnakeMove(PSnake snake)
{
	assert(snake);
	Snakepos pos = { 0 };
	switch (snake->dir)
	{
	case UP:
		pos.x = snake->pSnake->data.x;
		pos.y = snake->pSnake->data.y - 1;
		break;
	case DOWN:
		pos.x = snake->pSnake->data.x;
		pos.y = snake->pSnake->data.y + 1;
		break;
	case LEFT:
		pos.x = snake->pSnake->data.x - 2;
		pos.y = snake->pSnake->data.y;
		break;
	case RIGHT:
		pos.x = snake->pSnake->data.x + 2;
		pos.y = snake->pSnake->data.y;
		break;
	}
	//判断下一个节点是食物
	int foodstate = NextIsFood(snake, pos);
	//吃掉食物
	if (foodstate)
	{
		EatFood(snake);
		return;
	}
	//判断下一个节点不是食物
	//检测蛇是否撞墙
	KillByWall(snake, pos);
	//检测蛇是否撞到自己
	KillBySelf(snake, pos);
	//刷新蛇的身体
	RefreshSnake1(snake, pos);
}

/// <summary>
/// 退出游戏
/// </summary>
/// <param name="snake">蛇的状态地址</param>
void GameIsEsc(PSnake snake)
{
	assert(snake);
	SetCurPos(64, 5);
	wprintf(L"是否继续游戏，按Y/N选择");
	while (1)
	{
		Sleep(200);
		//检测到Y-继续游戏
		if (GET_KEY(VK_Y))
		{
			SetCurPos(64, 5);
			wprintf(L"                       ");
			return;
		}
		//检测到N-结束游戏
		if (GET_KEY(VK_N))
		{
			SetCurPos(64, 5);
			wprintf(L"正常退出               ");
			snake->state = END_NORMAL;
			return;
		}
	}
}

/// <summary>
/// 游戏运行
/// </summary>
/// <param name="snake">蛇的状态地址</param>
void GameRun(PSnake snake)
{
	assert(snake);
	//打印帮助信息
	PrintHelpInfo();
	//游戏运行
	do
	{
		//打印分数
		SetCurPos(64, 8);
		wprintf(L"总分数：%d", snake->all_score);
		wprintf(L"当前食物数：%2d", snake->food_score);
		//检测按键
		if (GET_KEY(VK_UP) && (snake->dir != DOWN))
		{
			snake->dir = UP;
		}
		else if (GET_KEY(VK_DOWN) && (snake->dir != UP))
		{
			snake->dir = DOWN;
		}
		else if (GET_KEY(VK_LEFT) && (snake->dir != RIGHT))
		{
			snake->dir = LEFT;
		}
		else if (GET_KEY(VK_RIGHT) && (snake->dir != LEFT))
		{
			snake->dir = RIGHT;
		}
		else if (GET_KEY(VK_SPACE))
		{
			//暂停
			Pause();
		}
		else if (GET_KEY(VK_ESCAPE))
		{
			//退出游戏
			GameIsEsc(snake);
		}
		else if (GET_KEY(VK_F3))
		{
			//加速
			if (snake->sleep_time > 100)
			{
				snake->sleep_time -= 20;
				snake->food_score = 20 + ((snake->sleep_time / 20) * -1);
			}
		}
		else if (GET_KEY(VK_F4))
		{
			//减速
			if (snake->sleep_time < 300)
			{
				snake->sleep_time += 20;
				snake->food_score = 20 + ((snake->sleep_time / 20) * -1);
			}
		}
		//蛇身体的移动
		SnakeMove(snake);
		//游戏运行速度
		Sleep(snake->sleep_time);
	} while (snake->state == OK);
}

/// <summary>
/// 创建结束窗口
/// </summary>
void EndMep()
{
	int i = 0;
	//打印第一行
	SetCurPos(20, 10);
	for (i = 0;i < 15;i++)
	{
		wprintf(WALL);
	}
	//打印最后一行
	SetCurPos(20, 20);
	for (i = 0;i < 15;i++)
	{
		wprintf(WALL);
	}
	//打印左边
	for (i = 0;i < 10;i++)
	{
		SetCurPos(20, (i + 10));
		wprintf(WALL);
	}
	//打印右边
	for (i = 0;i < 10;i++)
	{
		SetCurPos(48, (i + 10));
		wprintf(WALL);
	}
}

/// <summary>
/// 释放链表和食物
/// </summary>
/// <param name="snake">蛇的状态地址</param>
void FreeSnake(PSnake snake)
{
	assert(snake);
	//释放链表
	PSnakeSList tempnode = snake->pSnake;
	while (snake->pSnake)
	{
		tempnode = snake->pSnake;
		snake->pSnake = snake->pSnake->next;
		free(tempnode);
		tempnode = NULL;
	}
	//释放食物
	free(snake->pFood);
	snake->pFood == NULL;
}

/// <summary>
/// 游戏结束
/// </summary>
/// <param name="snake">蛇的状态地址</param>
void GameEnd(PSnake snake)
{
	//打印结束信息
	EndMep();
	//打印死亡信息
	switch (snake->state)
	{
	case KILL_BY_WALL://撞墙
		SetCurPos(30, 13);
		wprintf(L"撞到了墙");
		break;
	case KILL_BY_SELF://咬到⾃⼰
		SetCurPos(30, 13);
		wprintf(L"咬到⾃⼰");
		break;
	case END_NORMAL://正常结束
		SetCurPos(30, 13);
		wprintf(L"主动退出");
		break;
	default:
		break;
	}
	//释放内存
	//释放链表和食物
	FreeSnake(snake);
}


